/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    LinearRegression.java
 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
 *
 */
package weka.classifiers.functions;

import weka.classifiers.Classifier;
import weka.core.Capabilities;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Matrix;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.RevisionUtils;
import weka.core.SelectedTag;
import weka.core.Tag;
import weka.core.Utils;
import weka.core.WeightedInstancesHandler;
import weka.core.Capabilities.Capability;
import weka.filters.Filter;
import weka.filters.supervised.attribute.NominalToBinary;
import weka.filters.unsupervised.attribute.ReplaceMissingValues;

import java.util.Enumeration;
import java.util.Vector;
import weka.core.Attribute;
import weka.core.FastVector;

/**
<!-- globalinfo-start -->
 * Class for using linear regression for prediction. Uses the Akaike criterion for model selection, and is able to deal with weighted instances.
 * <p/>
<!-- globalinfo-end -->
 *
<!-- options-start -->
 * Valid options are: <p/>
 *
 * <pre> -D
 *  Produce debugging output.
 *  (default no debugging output)</pre>
 *
 * <pre> -S &lt;number of selection method&gt;
 *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
 *  (default 0 = M5' method)</pre>
 *
 * <pre> -C
 *  Do not try to eliminate colinear attributes.
 * </pre>
 *
 * <pre> -R &lt;double&gt;
 *  Set ridge parameter (default 1.0e-8).
 * </pre>
 *
<!-- options-end -->
 *
 * @author Hongxia Fang(fanghongxia2008@ayhoo.cn)
 * @version $Revision: 1.24 $
 * 本方法(NewLinearRegression)是在LinearRegression的基础上进行改进的，
 * 原因是LinearRegression不能对类别为nominal类型的数据进行分类预测。
 * LinearRegression只能针对类别为Numeric的数据，而本方法可以适用于类别为nominal类型的数据。
 * 修改之处：
 * 1.增加了类成员变量m_NumClasses；
 * 2.在成员函数buildClassifier(）里面对其参数instances进行必要的预变换成为data。
 * 3.在regressionPrediction（）方法里面添加2句关于预测结果上下线的越界处理。(此处引起连续类别处理结果错误，已删除——代大攀)
 * 4.重写了Classifier基类的distributionForInstance（）方法。
 */
public class NewLinearRegression extends Classifier implements OptionHandler,
        WeightedInstancesHandler {

    /** for serialization */
    static final long serialVersionUID = -3364580862046573747L;
    /** Array for storing coefficients of linear regression. */
    private double[] m_Coefficients;
    /** Which attributes are relevant? */
    private boolean[] m_SelectedAttributes;
    /** Variable for storing transformed training data. */
    private Instances m_TransformedData;
    /** The filter for removing missing values. */
    private ReplaceMissingValues m_MissingFilter;
    /** The filter storing the transformation from nominal to
    binary attributes. */
    private NominalToBinary m_TransformFilter;
    /** The standard deviations of the class attribute */
    private double m_ClassStdDev;
    /** The mean of the class attribute */
    private double m_ClassMean;
    /** The index of the class attribute */
    private int m_ClassIndex;
    /** The attributes means */
    private double[] m_Means;
    /** The attribute standard deviations */
    private double[] m_StdDevs;
    /** True if debug output will be printed */
    private boolean b_Debug;
    /** The current attribute selection method */
    private int m_AttributeSelection;
    /** Attribute selection method: M5 method */
    public static final int SELECTION_M5 = 0;
    /** Attribute selection method: No attribute selection */
    public static final int SELECTION_NONE = 1;
    /** Attribute selection method: Greedy method */
    public static final int SELECTION_GREEDY = 2;
    /** Attribute selection methods */
    public static final Tag[] TAGS_SELECTION = {
        new Tag(SELECTION_NONE, "No attribute selection"),
        new Tag(SELECTION_M5, "M5 method"),
        new Tag(SELECTION_GREEDY, "Greedy method")
    };
    /** Try to eliminate correlated attributes? */
    private boolean m_EliminateColinearAttributes = true;
    /** Turn off all checks and conversions? */
    private boolean m_checksTurnedOff = false;
    /** The ridge parameter */
    private double m_Ridge = 1.0e-8;
    private int m_NumClasses;

    /**
     * Turns off checks for missing values, etc. Use with caution.
     * Also turns off scaling.
     */
    public void turnChecksOff() {

        m_checksTurnedOff = true;
    }

    /**
     * Turns on checks for missing values, etc. Also turns
     * on scaling.
     */
    public void turnChecksOn() {

        m_checksTurnedOff = false;
    }

    /**
     * Returns a string describing this classifier
     * @return a description of the classifier suitable for
     * displaying in the explorer/experimenter gui
     */
    public String globalInfo() {
        return "Class for using linear regression for prediction. Uses the Akaike "
                + "criterion for model selection, and is able to deal with weighted "
                + "instances.";
    }

    /**
     * Returns default capabilities of the classifier.
     *
     * @return      the capabilities of this classifier
     */
    public Capabilities getCapabilities() {
        Capabilities result = super.getCapabilities();

        // attributes
        result.enable(Capability.NOMINAL_ATTRIBUTES);
        result.enable(Capability.NUMERIC_ATTRIBUTES);
        result.enable(Capability.DATE_ATTRIBUTES);
        result.enable(Capability.MISSING_VALUES);

        // class
        result.enable(Capability.NOMINAL_CLASS);
        result.enable(Capability.NUMERIC_CLASS);
        result.enable(Capability.DATE_CLASS);
        result.enable(Capability.MISSING_CLASS_VALUES);

        return result;
    }

    /**
     * Builds a regression model for the given data.
     *
     * @param data the training data to be used for generating the
     * linear regression function
     * @throws Exception if the classifier could not be built successfully
     */
    public void buildClassifier(Instances instances) throws Exception {

        m_NumClasses = instances.numClasses();
        Instances data = new Instances(instances);
        String className = data.classAttribute().name();

        if (data.classAttribute().isNominal()) {
            FastVector myNewClassVector = new FastVector();
            myNewClassVector.addElement(new Attribute(className));
            Instances MyNewClassInstances = new Instances("", myNewClassVector, 0);
            double weight = 0.0;
            double[] vals;
            Instance tempinst;
            for (int i = 0; i < data.numInstances(); i++) {
                weight = data.instance(i).weight();
                vals = new double[1];
                vals[0] = data.instance(i).classValue();
                tempinst = new Instance(weight, vals);
                MyNewClassInstances.add(tempinst);
            }
            data.deleteClassAttribute();
            data = Instances.mergeInstances(data, MyNewClassInstances);
            data.setClass(data.attribute(className));
        }
        //--------------------------------预处理结束--------------------------------//
        //-------------------------------------------------------------------------//
        if (!m_checksTurnedOff) {
            // can classifier handle the data?
            getCapabilities().testWithFail(data);

            // remove instances with missing class
            data = new Instances(data);
            data.deleteWithMissingClass();
        }

        // Preprocess instances
        if (!m_checksTurnedOff) {
            m_TransformFilter = new NominalToBinary();
            m_TransformFilter.setInputFormat(data);
            data = Filter.useFilter(data, m_TransformFilter);
            m_MissingFilter = new ReplaceMissingValues();
            m_MissingFilter.setInputFormat(data);
            data = Filter.useFilter(data, m_MissingFilter);
            data.deleteWithMissingClass();
        } else {
            m_TransformFilter = null;
            m_MissingFilter = null;
        }

        m_ClassIndex = data.classIndex();
        m_TransformedData = data;

        // Turn all attributes on for a start
        m_SelectedAttributes = new boolean[data.numAttributes()];
        for (int i = 0; i < data.numAttributes(); i++) {
            if (i != m_ClassIndex) {
                m_SelectedAttributes[i] = true;
            }
        }
        m_Coefficients = null;

        // Compute means and standard deviations
        m_Means = new double[data.numAttributes()];
        m_StdDevs = new double[data.numAttributes()];
        for (int j = 0; j < data.numAttributes(); j++) {
            if (j != data.classIndex()) {
                m_Means[j] = data.meanOrMode(j);
                m_StdDevs[j] = Math.sqrt(data.variance(j));
                if (m_StdDevs[j] == 0) {
                    m_SelectedAttributes[j] = false;
                }
            }
        }

        m_ClassStdDev = Math.sqrt(data.variance(m_TransformedData.classIndex()));
        m_ClassMean = data.meanOrMode(m_TransformedData.classIndex());

        // Perform the regression
        findBestModel();

        // Save memory
        m_TransformedData = new Instances(data, 0);
    }

    /**
     * Classifies the given instance using the linear regression function.
     *
     * @param instance the test instance
     * @return the classification
     * @throws Exception if classification can't be done successfully
     */
    public double classifyInstance(Instance instance) throws Exception {

        // Transform the input instance
        Instance transformedInstance = instance;
        if (!m_checksTurnedOff) {
            m_TransformFilter.input(transformedInstance);
            m_TransformFilter.batchFinished();
            transformedInstance = m_TransformFilter.output();
            m_MissingFilter.input(transformedInstance);
            m_MissingFilter.batchFinished();
            transformedInstance = m_MissingFilter.output();
        }

        // Calculate the dependent variable from the regression model
        return regressionPrediction(transformedInstance,
                m_SelectedAttributes,
                m_Coefficients);
    }

    /**
     * Outputs the linear regression model as a string.
     *
     * @return the model as string
     */
    public String toString() {

        if (m_TransformedData == null) {
            return "Linear Regression: No model built yet.";
        }
        try {
            StringBuffer text = new StringBuffer();
            int column = 0;
            boolean first = true;

            text.append("\nLinear Regression Model\n\n");

            text.append(m_TransformedData.classAttribute().name() + " =\n\n");
            for (int i = 0; i < m_TransformedData.numAttributes(); i++) {
                if ((i != m_ClassIndex)
                        && (m_SelectedAttributes[i])) {
                    if (!first) {
                        text.append(" +\n");
                    } else {
                        first = false;
                    }
                    text.append(Utils.doubleToString(m_Coefficients[column], 12, 4)
                            + " * ");
                    text.append(m_TransformedData.attribute(i).name());
                    column++;
                }
            }
            text.append(" +\n"
                    + Utils.doubleToString(m_Coefficients[column], 12, 4));
            return text.toString();
        } catch (Exception e) {
            return "Can't print Linear Regression!";
        }
    }

    /**
     * Returns an enumeration describing the available options.
     *
     * @return an enumeration of all the available options.
     */
    public Enumeration listOptions() {

        Vector newVector = new Vector(4);
        newVector.addElement(new Option("\tProduce debugging output.\n"
                + "\t(default no debugging output)",
                "D", 0, "-D"));
        newVector.addElement(new Option("\tSet the attribute selection method"
                + " to use. 1 = None, 2 = Greedy.\n"
                + "\t(default 0 = M5' method)",
                "S", 1, "-S <number of selection method>"));
        newVector.addElement(new Option("\tDo not try to eliminate colinear"
                + " attributes.\n",
                "C", 0, "-C"));
        newVector.addElement(new Option("\tSet ridge parameter (default 1.0e-8).\n",
                "R", 1, "-R <double>"));
        return newVector.elements();
    }

    /**
     * Parses a given list of options. <p/>
     *
    <!-- options-start -->
     * Valid options are: <p/>
     *
     * <pre> -D
     *  Produce debugging output.
     *  (default no debugging output)</pre>
     *
     * <pre> -S &lt;number of selection method&gt;
     *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
     *  (default 0 = M5' method)</pre>
     *
     * <pre> -C
     *  Do not try to eliminate colinear attributes.
     * </pre>
     *
     * <pre> -R &lt;double&gt;
     *  Set ridge parameter (default 1.0e-8).
     * </pre>
     *
    <!-- options-end -->
     *
     * @param options the list of options as an array of strings
     * @throws Exception if an option is not supported
     */
    public void setOptions(String[] options) throws Exception {

        String selectionString = Utils.getOption('S', options);
        if (selectionString.length() != 0) {
            setAttributeSelectionMethod(new SelectedTag(Integer.parseInt(selectionString),
                    TAGS_SELECTION));
        } else {
            setAttributeSelectionMethod(new SelectedTag(SELECTION_M5,
                    TAGS_SELECTION));
        }
        String ridgeString = Utils.getOption('R', options);
        if (ridgeString.length() != 0) {
            setRidge(new Double(ridgeString).doubleValue());
        } else {
            setRidge(1.0e-8);
        }
        setDebug(Utils.getFlag('D', options));
        setEliminateColinearAttributes(!Utils.getFlag('C', options));
    }

    /**
     * Returns the coefficients for this linear model.
     *
     * @return the coefficients for this linear model
     */
    public double[] coefficients() {

        double[] coefficients = new double[m_SelectedAttributes.length + 1];
        int counter = 0;
        for (int i = 0; i < m_SelectedAttributes.length; i++) {
            if ((m_SelectedAttributes[i]) && ((i != m_ClassIndex))) {
                coefficients[i] = m_Coefficients[counter++];
            }
        }
        coefficients[m_SelectedAttributes.length] = m_Coefficients[counter];
        return coefficients;
    }

    /**
     * Gets the current settings of the classifier.
     *
     * @return an array of strings suitable for passing to setOptions
     */
    public String[] getOptions() {

        String[] options = new String[6];
        int current = 0;

        options[current++] = "-S";
        options[current++] = "" + getAttributeSelectionMethod().getSelectedTag().getID();
        if (getDebug()) {
            options[current++] = "-D";
        }
        if (!getEliminateColinearAttributes()) {
            options[current++] = "-C";
        }
        options[current++] = "-R";
        options[current++] = "" + getRidge();

        while (current < options.length) {
            options[current++] = "";
        }
        return options;
    }

    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String ridgeTipText() {
        return "The value of the Ridge parameter.";
    }

    /**
     * Get the value of Ridge.
     *
     * @return Value of Ridge.
     */
    public double getRidge() {

        return m_Ridge;
    }

    /**
     * Set the value of Ridge.
     *
     * @param newRidge Value to assign to Ridge.
     */
    public void setRidge(double newRidge) {

        m_Ridge = newRidge;
    }

    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String eliminateColinearAttributesTipText() {
        return "Eliminate colinear attributes.";
    }

    /**
     * Get the value of EliminateColinearAttributes.
     *
     * @return Value of EliminateColinearAttributes.
     */
    public boolean getEliminateColinearAttributes() {

        return m_EliminateColinearAttributes;
    }

    /**
     * Set the value of EliminateColinearAttributes.
     *
     * @param newEliminateColinearAttributes Value to assign to EliminateColinearAttributes.
     */
    public void setEliminateColinearAttributes(boolean newEliminateColinearAttributes) {

        m_EliminateColinearAttributes = newEliminateColinearAttributes;
    }

    /**
     * Get the number of coefficients used in the model
     *
     * @return the number of coefficients
     */
    public int numParameters() {
        return m_Coefficients.length - 1;
    }

    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String attributeSelectionMethodTipText() {
        return "Set the method used to select attributes for use in the linear "
                + "regression. Available methods are: no attribute selection, attribute "
                + "selection using M5's method (step through the attributes removing the one "
                + "with the smallest standardised coefficient until no improvement is observed "
                + "in the estimate of the error given by the Akaike "
                + "information criterion), and a greedy selection using the Akaike information "
                + "metric.";
    }

    /**
     * Sets the method used to select attributes for use in the
     * linear regression.
     *
     * @param method the attribute selection method to use.
     */
    public void setAttributeSelectionMethod(SelectedTag method) {

        if (method.getTags() == TAGS_SELECTION) {
            m_AttributeSelection = method.getSelectedTag().getID();
        }
    }

    /**
     * Gets the method used to select attributes for use in the
     * linear regression.
     *
     * @return the method to use.
     */
    public SelectedTag getAttributeSelectionMethod() {

        return new SelectedTag(m_AttributeSelection, TAGS_SELECTION);
    }

    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String debugTipText() {
        return "Outputs debug information to the console.";
    }

    /**
     * Controls whether debugging output will be printed
     *
     * @param debug true if debugging output should be printed
     */
    public void setDebug(boolean debug) {

        b_Debug = debug;
    }

    /**
     * Controls whether debugging output will be printed
     *
     * @return true if debugging output is printed
     */
    public boolean getDebug() {

        return b_Debug;
    }

    /**
     * Removes the attribute with the highest standardised coefficient
     * greater than 1.5 from the selected attributes.
     *
     * @param selectedAttributes an array of flags indicating which
     * attributes are included in the regression model
     * @param coefficients an array of coefficients for the regression
     * model
     * @return true if an attribute was removed
     */
    private boolean deselectColinearAttributes(boolean[] selectedAttributes,
            double[] coefficients) {

        double maxSC = 1.5;
        int maxAttr = -1, coeff = 0;
        for (int i = 0; i < selectedAttributes.length; i++) {
            if (selectedAttributes[i]) {
                double SC = Math.abs(coefficients[coeff] * m_StdDevs[i]
                        / m_ClassStdDev);
                if (SC > maxSC) {
                    maxSC = SC;
                    maxAttr = i;
                }
                coeff++;
            }
        }
        if (maxAttr >= 0) {
            selectedAttributes[maxAttr] = false;
            if (b_Debug) {
                System.out.println("Deselected colinear attribute:" + (maxAttr + 1)
                        + " with standardised coefficient: " + maxSC);
            }
            return true;
        }
        return false;
    }

    /**
     * Performs a greedy search for the best regression model using
     * Akaike's criterion.
     *
     * @throws Exception if regression can't be done
     */
    private void findBestModel() throws Exception {

        // For the weighted case we still use numInstances in
        // the calculation of the Akaike criterion.
        int numInstances = m_TransformedData.numInstances();

        if (b_Debug) {
            System.out.println((new Instances(m_TransformedData, 0)).toString());
        }

        // Perform a regression for the full model, and remove colinear attributes
        do {
            m_Coefficients = doRegression(m_SelectedAttributes);
        } while (m_EliminateColinearAttributes
                && deselectColinearAttributes(m_SelectedAttributes, m_Coefficients));

        // Figure out current number of attributes + 1. (We treat this model
        // as the full model for the Akaike-based methods.)
        int numAttributes = 1;
        for (int i = 0; i < m_SelectedAttributes.length; i++) {
            if (m_SelectedAttributes[i]) {
                numAttributes++;
            }
        }

        double fullMSE = calculateSE(m_SelectedAttributes, m_Coefficients);
        double akaike = (numInstances - numAttributes) + 2 * numAttributes;
        if (b_Debug) {
            System.out.println("Initial Akaike value: " + akaike);
        }

        boolean improved;
        int currentNumAttributes = numAttributes;
        switch (m_AttributeSelection) {

            case SELECTION_GREEDY:

                // Greedy attribute removal
                do {
                    boolean[] currentSelected = (boolean[]) m_SelectedAttributes.clone();
                    improved = false;
                    currentNumAttributes--;

                    for (int i = 0; i < m_SelectedAttributes.length; i++) {
                        if (currentSelected[i]) {

                            // Calculate the akaike rating without this attribute
                            currentSelected[i] = false;
                            double[] currentCoeffs = doRegression(currentSelected);
                            double currentMSE = calculateSE(currentSelected, currentCoeffs);
                            double currentAkaike = currentMSE / fullMSE
                                    * (numInstances - numAttributes)
                                    + 2 * currentNumAttributes;
                            if (b_Debug) {
                                System.out.println("(akaike: " + currentAkaike);
                            }

                            // If it is better than the current best
                            if (currentAkaike < akaike) {
                                if (b_Debug) {
                                    System.err.println("Removing attribute " + (i + 1)
                                            + " improved Akaike: " + currentAkaike);
                                }
                                improved = true;
                                akaike = currentAkaike;
                                System.arraycopy(currentSelected, 0,
                                        m_SelectedAttributes, 0,
                                        m_SelectedAttributes.length);
                                m_Coefficients = currentCoeffs;
                            }
                            currentSelected[i] = true;
                        }
                    }
                } while (improved);
                break;

            case SELECTION_M5:

                // Step through the attributes removing the one with the smallest
                // standardised coefficient until no improvement in Akaike
                do {
                    improved = false;
                    currentNumAttributes--;

                    // Find attribute with smallest SC
                    double minSC = 0;
                    int minAttr = -1, coeff = 0;
                    for (int i = 0; i < m_SelectedAttributes.length; i++) {
                        if (m_SelectedAttributes[i]) {
                            double SC = Math.abs(m_Coefficients[coeff] * m_StdDevs[i]
                                    / m_ClassStdDev);
                            if ((coeff == 0) || (SC < minSC)) {
                                minSC = SC;
                                minAttr = i;
                            }
                            coeff++;
                        }
                    }

                    // See whether removing it improves the Akaike score
                    if (minAttr >= 0) {
                        m_SelectedAttributes[minAttr] = false;
                        double[] currentCoeffs = doRegression(m_SelectedAttributes);
                        double currentMSE = calculateSE(m_SelectedAttributes, currentCoeffs);
                        double currentAkaike = currentMSE / fullMSE
                                * (numInstances - numAttributes)
                                + 2 * currentNumAttributes;
                        if (b_Debug) {
                            System.out.println("(akaike: " + currentAkaike);
                        }

                        // If it is better than the current best
                        if (currentAkaike < akaike) {
                            if (b_Debug) {
                                System.err.println("Removing attribute " + (minAttr + 1)
                                        + " improved Akaike: " + currentAkaike);
                            }
                            improved = true;
                            akaike = currentAkaike;
                            m_Coefficients = currentCoeffs;
                        } else {
                            m_SelectedAttributes[minAttr] = true;
                        }
                    }
                } while (improved);
                break;

            case SELECTION_NONE:
                break;
        }
    }

    /**
     * Calculate the squared error of a regression model on the
     * training data
     *
     * @param selectedAttributes an array of flags indicating which
     * attributes are included in the regression model
     * @param coefficients an array of coefficients for the regression
     * model
     * @return the mean squared error on the training data
     * @throws Exception if there is a missing class value in the training
     * data
     */
    private double calculateSE(boolean[] selectedAttributes,
            double[] coefficients) throws Exception {

        double mse = 0;
        for (int i = 0; i < m_TransformedData.numInstances(); i++) {
            double prediction = regressionPrediction(m_TransformedData.instance(i),
                    selectedAttributes,
                    coefficients);
            double error = prediction - m_TransformedData.instance(i).classValue();
            mse += error * error;
        }
        return mse;
    }

    /**
     * Calculate the dependent value for a given instance for a
     * given regression model.
     *
     * @param transformedInstance the input instance
     * @param selectedAttributes an array of flags indicating which
     * attributes are included in the regression model
     * @param coefficients an array of coefficients for the regression
     * model
     * @return the regression value for the instance.
     * @throws Exception if the class attribute of the input instance
     * is not assigned
     */
    private double regressionPrediction(Instance transformedInstance,
            boolean[] selectedAttributes,
            double[] coefficients)
            throws Exception {

        double result = 0;
        int column = 0;
        for (int j = 0; j < transformedInstance.numAttributes(); j++) {
            if ((m_ClassIndex != j)
                    && (selectedAttributes[j])) {
                result += coefficients[column] * transformedInstance.value(j);
                column++;
            }
        }
        result += coefficients[column];

        return result;
    }

    /**
     * Calculate a linear regression using the selected attributes
     *
     * @param selectedAttributes an array of booleans where each element
     * is true if the corresponding attribute should be included in the
     * regression.
     * @return an array of coefficients for the linear regression model.
     * @throws Exception if an error occurred during the regression.
     */
    private double[] doRegression(boolean[] selectedAttributes)
            throws Exception {

        if (b_Debug) {
            System.out.print("doRegression(");
            for (int i = 0; i < selectedAttributes.length; i++) {
                System.out.print(" " + selectedAttributes[i]);
            }
            System.out.println(" )");
        }
        int numAttributes = 0;
        for (int i = 0; i < selectedAttributes.length; i++) {
            if (selectedAttributes[i]) {
                numAttributes++;
            }
        }

        // Check whether there are still attributes left
        Matrix independent = null, dependent = null;
        double[] weights = null;
        if (numAttributes > 0) {
            independent = new Matrix(m_TransformedData.numInstances(),
                    numAttributes);
            dependent = new Matrix(m_TransformedData.numInstances(), 1);
            for (int i = 0; i < m_TransformedData.numInstances(); i++) {
                Instance inst = m_TransformedData.instance(i);
                int column = 0;
                for (int j = 0; j < m_TransformedData.numAttributes(); j++) {
                    if (j == m_ClassIndex) {
                        dependent.setElement(i, 0, inst.classValue());
                    } else {
                        if (selectedAttributes[j]) {
                            double value = inst.value(j) - m_Means[j];

                            // We only need to do this if we want to
                            // scale the input
                            if (!m_checksTurnedOff) {
                                value /= m_StdDevs[j];
                            }
                            independent.setElement(i, column, value);
                            column++;
                        }
                    }
                }
            }

            // Grab instance weights
            weights = new double[m_TransformedData.numInstances()];
            for (int i = 0; i < weights.length; i++) {
                weights[i] = m_TransformedData.instance(i).weight();
            }
        }

        // Compute coefficients (note that we have to treat the
        // intercept separately so that it doesn't get affected
        // by the ridge constant.)
        double[] coefficients = new double[numAttributes + 1];
        if (numAttributes > 0) {
            double[] coeffsWithoutIntercept =
                    independent.regression(dependent, weights, m_Ridge);
            System.arraycopy(coeffsWithoutIntercept, 0, coefficients, 0,
                    numAttributes);
        }
        coefficients[numAttributes] = m_ClassMean;

        // Convert coefficients into original scale
        int column = 0;
        for (int i = 0; i < m_TransformedData.numAttributes(); i++) {
            if ((i != m_TransformedData.classIndex())
                    && (selectedAttributes[i])) {

                // We only need to do this if we have scaled the
                // input.
                if (!m_checksTurnedOff) {
                    coefficients[column] /= m_StdDevs[i];
                }

                // We have centred the input
                coefficients[coefficients.length - 1] -=
                        coefficients[column] * m_Means[i];
                column++;
            }
        }

        return coefficients;
    }

    /**
     * Returns the revision string.
     *
     * @return		the revision
     */
    public String getRevision() {
        return RevisionUtils.extract("$Revision: 1.24 $");
    }

    @Override
    public double[] distributionForInstance(Instance instance) throws Exception {

        double[] dist = new double[m_NumClasses];
        switch (instance.classAttribute().type()) {
            case Attribute.NOMINAL:
                double classification = classifyInstance(instance);
                if (Instance.isMissingValue(classification)) {
                    return dist;
                } else {
                    if (classification <= 0.0) {
                        dist[0] = 1.0;
                    }
                    if (classification >= (m_NumClasses - 1.0)) {
                        dist[m_NumClasses - 1] = 1.0;
                    }
                    if (classification > 0.0 && classification < (m_NumClasses - 1.0)) {
                        dist[(int) Math.ceil(classification)] = classification - Math.floor(classification);
                        dist[(int) Math.floor(classification)] = Math.ceil(classification) - classification;
                    }
                }
                return dist;
            case Attribute.NUMERIC:
                dist[0] = classifyInstance(instance);
                return dist;
            default:
                return dist;
        }
    }

    /**
     * Generates a linear regression function predictor.
     *
     * @param argv the options
     */
    public static void main(String argv[]) {
        runClassifier(new NewLinearRegression(), argv);
    }
}
